#ifndef __MOXIABOT_MOXIABOT_ANGEL_BIND_HPP__
#define __MOXIABOT_MOXIABOT_ANGEL_BIND_HPP__
#include <angelscript.h>
#include <iostream>
#include <string>
#include <vector>

namespace asbind
{
	struct helper
	{
		template<typename R, typename T>
		static inline R tranform(T t)
		{
			union
			{
				R r;
				T t;
			}helper;
			helper.t = t;
			return helper.r;
		}
	};
	struct FunctionInfo 
	{
		std::string obj_decl;

		std::string fn_decl;
		asSFuncPtr fn_ptr; 
		asECallConvTypes fn_call_type;
	};
	struct ObjInfo
	{
		bool reg_obj;

		std::string obj_decl;
		int obj_size;
		int obj_type;

		std::string property_decl;
		int property_offset;
	};
	typedef std::vector<FunctionInfo> FnVector;
	typedef std::vector<ObjInfo> ObjVector;
	class scope
	{
	public:
		scope& scope::operator,(scope s)
		{
			std::cout << __FUNCTION__ << std::endl;
			for (ObjVector::iterator it = s.objs_.begin(); it != s.objs_.end(); it++)
			{
				objs_.push_back(*it);
			}
			for (FnVector::iterator it = s.functions_.begin(); it != s.functions_.end(); it++)
			{
				functions_.push_back(*it);
			}
			return *this;
		}
		void export_to_script(asIScriptEngine* engine)
		{
			for (ObjVector::iterator it = objs_.begin(); it != objs_.end(); it++)
			{
				if (it->reg_obj)
				{
					engine->RegisterObjectType(it->obj_decl.c_str(), it->obj_size, it->obj_type);
				}
				else if (!it->obj_decl.empty())
				{
					engine->RegisterObjectProperty(it->obj_decl.c_str(), it->property_decl.c_str(), it->property_offset);
				}
				else
				{
					engine->RegisterGlobalProperty(it->property_decl.c_str(), reinterpret_cast<void*>(it->property_offset));
				}
			}
			
			for (FnVector::iterator it = functions_.begin(); it != functions_.end(); it++)
			{
				if (!it->obj_decl.empty())
				{
					engine->RegisterObjectMethod(it->obj_decl.c_str(), it->fn_decl.c_str(), it->fn_ptr, it->fn_call_type);
				}
				else
				{
					engine->RegisterGlobalFunction(it->fn_decl.c_str(), it->fn_ptr, it->fn_call_type);
				}
			}
		}
	public:
		inline FnVector& get_functions() { return functions_; }
		inline ObjVector& get_objects() { return objs_; }
	protected:
		ObjVector objs_;
		FnVector functions_;
	};
	template<typename T, int obj_type = asOBJ_REF | asOBJ_NOCOUNT>
	class class_ : public scope
	{
	public:
		class_(const char* name)
		{
			std::cout << __FUNCTION__ << " - " << name << std::endl;

			ObjInfo obj_info;

			obj_info.reg_obj = true;

			obj_info.obj_decl = name;
			obj_info.obj_size = sizeof(T);
			obj_info.obj_type = obj_type;

			objs_.push_back(obj_info);
		}
		template<typename F>
		class_& def(const char* name, F f, asECallConvTypes call_type = asCALL_THISCALL)
		{
			std::cout << __FUNCTION__ << " - " << name << std::endl;
			
			FunctionInfo function_info;

			function_info.obj_decl = objs_[0].obj_decl;
			function_info.fn_decl = name;
			function_info.fn_ptr = asSMethodPtr<sizeof(f)>::Convert(f);
			function_info.fn_call_type = call_type;

			functions_.push_back(function_info);

			return *this;
		}
		template<typename T>
		class_& property(const char* name, T offset)
		{

			ObjInfo obj_info;

			obj_info.reg_obj = false;

			obj_info.obj_decl = objs_[0].obj_decl;
			obj_info.property_decl = name;
			obj_info.property_offset = helper::tranform<int>(offset);

			objs_.push_back(obj_info);

			return *this;
		}
	};
	class export_
	{
	public:
		export_(asIScriptEngine* engine)
			:as_engine_(engine)
		{}
		void operator[](scope s)
		{
			std::cout << __FUNCTION__ << std::endl;
			
			s.export_to_script(as_engine_);
		}
	private:
		asIScriptEngine* as_engine_;
	};
	inline export_ export_to_script(asIScriptEngine* engine)
	{
		return export_(engine);
	}
	template<typename F>
	scope def(char const* name, F f, asECallConvTypes call_type = asCALL_CDECL)
	{
		scope s;

		FunctionInfo info;
		info.fn_decl = name;
		info.fn_ptr = asFUNCTION(f);
		info.fn_call_type =call_type;
	
		s.get_functions().push_back(info);

		return s;
	}
	scope property(const char* decl, void* offset)
	{
		scope s;

		ObjInfo obj_info;

		obj_info.reg_obj = false;

		obj_info.property_decl = decl;
		obj_info.property_offset = helper::tranform<int>(offset);

		s.get_objects().push_back(obj_info);

		return s;
	}
}
#endif//__MOXIABOT_MOXIABOT_ANGEL_BIND_HPP__